package simulador;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;

public class Simulador {

    public static ArrayList<EstadoFijo> ParticionesFijas(ArrayList<Proceso> procesos, ArrayList<Particion> particiones, Algoritmo algoritmo) {
        ArrayList<EstadoFijo> estados = new ArrayList<>();
        int t = 0;
        ArrayList<Asignacion> asignaciones = new ArrayList<>();
        ArrayList<Proceso> listos = new ArrayList<>();
        ArrayList<Proceso> terminados = new ArrayList<>();
        ArrayList<Particion> disponibles = new ArrayList<>(particiones);
        boolean terminado = false;
        while (!terminado) {
            //restar 1 instante al restante de cada proceso en memoria y reacomodar terminados, disponibles y asignaciones
            Iterator<Asignacion> itA = asignaciones.iterator();
            while (itA.hasNext()) {
                Asignacion a = itA.next();
                a.setRestante(a.getRestante() - 1);
                if (a.getRestante() == 0) {
                    terminados.add(a.getProceso());
                    disponibles.add(a.getParticion());
                    itA.remove();
                }
            }

            //ordenar disponibles
            Collections.sort(disponibles, new ParticionComparator());

            //agregar procesos a listos
            for (Proceso p : procesos) {
                if (p.getArribo() == t) {
                    listos.add(p);
                }
            }

            //asignar procesos a particiones
            Iterator<Proceso> itListos = listos.iterator();
            while (itListos.hasNext()) {
                Proceso pro = itListos.next();
                Particion elegida = null;
                Iterator<Particion> itDisponibles = disponibles.iterator();
                while (itDisponibles.hasNext()) {
                    Particion par = itDisponibles.next();
                    //si el proceso entra en la particion
                    if (pro.getTamano() <= par.getTamano()) {
                        switch (algoritmo) {
                            case FirstFit:
                                //si no se ha elegido partición
                                if (elegida == null) {
                                    elegida = par;
                                }
                                break;
                            case BestFit:
                                //si se ha elegido partición, pero la nueva es de menor tamaño,
                                //o si no se ha elegido partición
                                if ((elegida != null && par.getTamano() < elegida.getTamano())
                                        || elegida == null) {
                                    elegida = par;
                                }
                                break;
                            case WorstFit:
                                //si se ha elegido partición, pero la nueva es de mayor tamaño,
                                //o si no se ha elegido partición
                                if ((elegida != null && par.getTamano() > elegida.getTamano())
                                        || elegida == null) {
                                    elegida = par;
                                }
                                break;
                        }
                    }
                }
                if (elegida != null) {
                    asignaciones.add(new Asignacion(elegida, pro, pro.getDuracion()));
                    itListos.remove();
                    disponibles.remove(elegida);
                }
            }

            //guardar el estado actual
            ArrayList<Asignacion> as = new ArrayList<>();
            Iterator<Asignacion> itAsig = asignaciones.iterator();
            while (itAsig.hasNext()) {
                Asignacion a = itAsig.next();
                Asignacion b = new Asignacion(a.getParticion(), a.getProceso(), a.getRestante());
                as.add(b);
            }
            ArrayList<Proceso> li = new ArrayList<>(listos);
            ArrayList<Proceso> te = new ArrayList<>(terminados);
            estados.add(new EstadoFijo(t, as, li, te));

            //fijarse si se terminó de simular
            if (terminados.size() == procesos.size()) {
                terminado = true;
            } else {
                t++;
            }
        }
        return estados;
    }

    public static ArrayList<EstadoVariable> ParticionesVariables(ArrayList<Proceso> procesos, int memoria, Algoritmo algoritmo, boolean compactar) {
        ArrayList<EstadoVariable> estados = new ArrayList<>();
        int t = 0;
        ArrayList<Asignacion> asignaciones = new ArrayList<>();
        ArrayList<Proceso> listos = new ArrayList<>();
        ArrayList<Proceso> terminados = new ArrayList<>();
        Espacio inicial = new Espacio(0, memoria);
        ArrayList<Espacio> espacios = new ArrayList<>();
        espacios.add(inicial);
        boolean terminado = false;
        while (!terminado) {
            //restar 1 instante al restante de cada proceso en memoria y reacomodar terminados, disponibles y asignaciones
            Iterator<Asignacion> itAsig = asignaciones.iterator();
            while (itAsig.hasNext()) {
                Asignacion a = itAsig.next();
                a.setRestante(a.getRestante() - 1);
                if (a.getRestante() == 0) {
                    terminados.add(a.getProceso());
                    espacios.add(new Espacio(a.getParticion().getInicio(), a.getParticion().getTamano()));
                    itAsig.remove();
                }
            }

            //ordenar espacios
            Collections.sort(espacios, new EspacioComparator());

            //combinar espacios contiguos
            if (espacios.size() > 1) {
                Iterator<Espacio> eit = espacios.iterator();
                Espacio first = eit.next();
                ArrayList<Espacio> nuevoEspacios = new ArrayList<>();
                nuevoEspacios.add(first);
                Espacio ultimoNuevo = nuevoEspacios.get(nuevoEspacios.size() - 1);
                while (eit.hasNext()) {
                    Espacio current = eit.next();
                    if (ultimoNuevo.getInicio() + ultimoNuevo.getTamano() == current.getInicio()) {
                        ultimoNuevo.setTamano(ultimoNuevo.getTamano() + current.getTamano());
                    } else {
                        nuevoEspacios.add(current);
                        ultimoNuevo = nuevoEspacios.get(nuevoEspacios.size() - 1);
                    }
                }
                espacios = nuevoEspacios;
            }

            //agregar procesos a listos
            for (Proceso p : procesos) {
                if (p.getArribo() == t) {
                    listos.add(p);
                }
            }

            //buscar espacios libres adecuados para asignarlos a procesos
            Iterator<Proceso> itPro = listos.iterator();
            while (itPro.hasNext()) {
                Proceso pro = itPro.next();
                Espacio elegido = null;
                Iterator<Espacio> itEsp = espacios.iterator();
                while (itEsp.hasNext()) {
                    Espacio esp = itEsp.next();
                    //si el proceso entra en el espacio
                    if (pro.getTamano() <= esp.getTamano()) {
                        switch (algoritmo) {
                            case FirstFit:
                                //si no se ha elegido espacio
                                if (elegido == null) {
                                    elegido = esp;
                                }
                                break;
                            case BestFit:
                                //si se ha elegido espacio, pero el nuevo es de menor tamaño,
                                //o si no se ha elegido espacio
                                if ((elegido != null && esp.getTamano() < elegido.getTamano())
                                        || elegido == null) {
                                    elegido = esp;
                                }
                                break;
                            case WorstFit:
                                //si se ha elegido espacio, pero el nuevo es de mayor tamaño,
                                //o si no se ha elegido espacio
                                if ((elegido != null && esp.getTamano() > elegido.getTamano())
                                        || elegido == null) {
                                    elegido = esp;
                                }
                                break;
                        }
                    }
                }

                //asignar proceso a espacio
                if (elegido != null) {
                    asignaciones.add(new Asignacion(new Particion(0, elegido.getInicio(), pro.getTamano()), pro, pro.getDuracion()));
                    if (pro.getTamano() == elegido.getTamano()) {
                        espacios.remove(elegido);
                    } else {
                        espacios.set(espacios.indexOf(elegido), new Espacio(elegido.getInicio() + pro.getTamano(), elegido.getTamano() - pro.getTamano()));
                    }
                    itPro.remove();
                }
                //compactar si corresponde
                else if (espacios.size() > 1 & compactar) {
                    Iterator<Espacio> itEspa = espacios.iterator();
                    int tamEsp = 0;
                    while (itEspa.hasNext()) {
                        tamEsp += itEspa.next().getTamano();
                    }
                    if (pro.getTamano() <= tamEsp) {
                        //ordenar asignaciones
                        Collections.sort(asignaciones, new AsignacionComparator());
                        //mover asignaciones a la izquierda
                        Iterator<Asignacion> itAs = asignaciones.iterator();
                        int posicion = 0;
                        while (itAs.hasNext()) {
                            Asignacion actual = itAs.next();
                            actual.setParticion(new Particion(0, posicion, actual.getParticion().getTamano()));
                            posicion += actual.getParticion().getTamano();
                        }
                        //Compactar espacios
                        Iterator<Espacio> itEs = espacios.iterator();
                        while (itEs.hasNext()) {
                            itEs.next();
                            itEs.remove();
                        }
                        //ver si el proceso entra en el espacio nuevo
                        asignaciones.add(new Asignacion(new Particion(0, posicion, pro.getTamano()), pro, pro.getDuracion()));
                        if (pro.getTamano() < tamEsp) {
                            espacios.add(0, new Espacio(posicion + pro.getTamano(), tamEsp - pro.getTamano()));
                        }
                        itPro.remove();
                    }

                }
            }

            //guardar el estado actual
            ArrayList<Asignacion> as = new ArrayList<>();
            Iterator<Asignacion> itAs = asignaciones.iterator();
            while (itAs.hasNext()) {
                Asignacion a = itAs.next();
                Asignacion b = new Asignacion(a.getParticion(), a.getProceso(), a.getRestante());
                as.add(b);
            }
            //ArrayList<Espacio> es = new ArrayList<>(espacios);
            ArrayList<Espacio> es = new ArrayList<>();
            Iterator<Espacio> itEs = espacios.iterator();
            while (itEs.hasNext()) {
                Espacio a = itEs.next();
                Espacio b = new Espacio(a.getInicio(), a.getTamano());
                es.add(b);
            }
            ArrayList<Proceso> li = new ArrayList<>(listos);
            ArrayList<Proceso> te = new ArrayList<>(terminados);
            estados.add(new EstadoVariable(t, as, es, li, te));

            //fijarse si se terminó de simular
            if (terminados.size() == procesos.size()) {
                terminado = true;
            } else {
                t++;
            }
        }
        return estados;
    }
}
